import collections
from copy import deepcopy
import warnings

import numpy
import tables

import histogram.h1d as h1d
import histogram.h2d as h2d
import histogram.h3d as h3d
import histogram.htypes as ht

from histogram.histcoll import HistogramCollection
from histogram.exceptions import HistogramIOError

warnings.simplefilter('ignore', tables.NaturalNameWarning)

def dump(hin, path, complevel=1):
    if type(hin) == type(HistogramCollection()):
        h = hin
    elif type(hin) in ht.htypes:
        h = HistogramCollection()
        h.add(hin)
    elif isinstance(hin, collections.Mapping):
        h = HistogramCollection()
        if '__hmod__' in hin:
            h.module_name = hin['__hmod__']
        
        for k in hin:
            if k == '__hmod__':
                continue
            h.add(hin[k])                    
    elif isinstance(hin, collections.Sequence):
        h = HistogramCollection(hin)
    else:
        msg = "histogram.dump() does not support output of type {0}"
        raise HistogramIOError(msg.format(type(h)))
      
    if not path.endswith('.hpt'):
        path += '.hpt'

    with tables.openFile(path, mode='w',
                         filters=tables.Filters(complevel=complevel, 
                                                complib='lzo')) as fp:
        if h.module_name is not None:
            g = fp.createGroup(fp.root, 'hmod')
            fp.setNodeAttr(g, 'module_path', h.module_name)
            
        for k in h.names():
            h[k].flush()
            
            g = fp.createGroup(fp.root, k)
            try:                
                fp.createArray(g, 'bins', h[k].bins())
                if type(h[k]) == h1d.Histogram1D:
                    fp.createArray(g, 'low_edges', h[k].low_edges)
                else:
                    fp.createArray(g, 'low_edges_x', h[k].low_edges_x)
                    fp.createArray(g, 'low_edges_y', h[k].low_edges_y)
                    
                    if type(h[k]) == h3d.Histogram3D:
                        fp.createArray(g, 'low_edges_z', h[k].low_edges_z)
                
            except:
                print 'CRAIGB :', path, k
                raise

            fp.setNodeAttr(g, 'histobj', h[k])


def load(path, names=[]):
    if not tables.isPyTablesFile(path):
        msg = "Path `{0}' does not point to a PyTables file".format(path)
        raise HistogramIOError(msg)

    h = HistogramCollection()

    with tables.openFile(path) as fp:
        for g in fp.iterNodes(fp.root, 'Group'):
            k = g._v_name

            if len(names) > 0 and k not in names:
                continue

            if k == 'hmod':
                hmod = fp.getNodeAttr(g, 'module_path')
                hmod_type = type(hmod)
                if hmod_type != type("hi!") and \
                        hmod_type != type(numpy.string_('hi')):
                    h.module_name = hmod[0]
                else:
                    h.module_name = str(hmod)

                continue
            
            h.add(fp.getNodeAttr(g, 'histobj'))

            h[k]._bins = g.bins.read()

            if type(h[k]) == h1d.Histogram1D:
                h[k].low_edges = g.low_edges.read()

            elif type(h[k]) == h2d.Histogram2D:
                try:
                    h[k].low_edges_x = g.low_edges_x.read()
                except tables.exceptions.NoSuchNodeError:
                    h[k].low_edges_x, h[k].low_edges_y = g.low_edges.read()
                else:
                    h[k].low_edges_y = g.low_edges_y.read()

            elif type(h[k]) == h3d.Histogram3D:
                try:
                    h[k].low_edges_x = g.low_edges_x.read()
                except tables.exceptions.NoSuchNodeError:
                    h[k].low_edges_x, h[k].low_edges_y, \
                        h[k].low_edges_z = g.low_edges.read()
                else:
                    h[k].low_edges_y = g.low_edges_y.read()
                    h[k].low_edges_z = g.low_edges_z.read()
            
            

    if h.module_name is not None:
        update_hist_attrs(h)

    return h

def copy(h):
    if type(h) in ht.htypes:
        h = [h]

    ret = []
    for ih in h:
        htmp = deepcopy(ih)
        htmp._bins = numpy.copy(ih._bins)

        if type(htmp) == h1d.Histogram1D:
            htmp.low_edges = numpy.copy(ih.low_edges)

        elif type(htmp) == h2d.Histogram2D:
            htmp.low_edges_x = numpy.copy(ih.low_edges_x)
            htmp.low_edges_y = numpy.copy(ih.low_edges_y)

        elif type(htmp) == h3d.Histogram3D:
            htmp.low_edges_x = numpy.copy(ih.low_edges_x)
            htmp.low_edges_y = numpy.copy(ih.low_edges_y)
            htmp.low_edges_z = numpy.copy(ih.low_edges_z)

        ret.append(htmp)

    if len(ret) == 1:
        return ret[0]
    else:
        return ret

def update_hist_attrs(hc):
    if hc.module_name is None:
        return hd

    import sys
    import os
    sys.path.append(os.getcwd())
        
    try:
        module = __import__(hc.module_name)
    except ImportError, e:
        msg = "HistogramCollection has module_name `{}', \n"
        msg += "but module could not be imported"
        warnings.warn(msg.format(hc.module_name))
        return

    if hasattr(module, 'hcolls'):
        hcolls = module.hcolls
        hnames = module.hnames
    else:
        hcolls = module.histdef.hcolls        
        hnames = module.histdef.hnames
        
    update_hists = {}
    for name in hc.names():
        if name not in hnames:
            warnings.warn("Histogram `{}' not found in hcolls".format(name))
        elif len(hnames[name]) > 1:
            msg = "Multiple collections have a histogram with name `{}'\n"
            msg += "in module `{}'; they are " + ' '.join(hnames[name])
            raise NotImplementedError(msg.format(name, hc.module_name))
        else:
            collname = list(hnames[name])[0]
            update_hists[name] = hcolls[collname][name]

    attrs = 'title xlabel xunits ylabel yunits zlabel zunits logz'.split()
    for name, uh in update_hists.iteritems():
        for attr in attrs:
            if hasattr(hc[name], attr) and hasattr(uh, attr):
                setattr(hc[name], attr, getattr(uh, attr))
                
    

    
        
